// Copyright (c) 2014 Liyong Zou
//
// Permission is hereby granted, free of charge, to any person obtaining a
// copy of this software and associated documentation files (the "Software"),
// to deal in the Software without restriction, including without limitation
// the rights to use, copy, modify, merge, publish, distribute, sublicense,
// and/or sell copies of the Software, and to permit persons to whom the
// Software is furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included
// in all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF
// ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED
// TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A
// PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT
// SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR
// ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
// ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE
// OR OTHER DEALINGS IN THE SOFTWARE.



#ifndef SCOPE_HPP
#define SCOPE_HPP

#include "from_lua.hpp"
#include "object.hpp"
#include "function.hpp"

#include <list>
#include <cstring>

namespace luaglue
{

struct export_symbol
{
    // push scope table before call this function
    virtual void export_symbol_to_scope(lua_State *L) const = 0;

    export_symbol &operator,(export_symbol &lst)
    {
        symlst.splice(symlst.end(), lst.symlst);

        return *this;
    }

    virtual ~export_symbol()
    {

    }

    std::list<shared_ptr<export_symbol> > symlst;
};

template <class T>
struct export_func: public export_symbol
{
    static export_symbol &new_export_func(const std::string fn, T func)
    {
        shared_ptr<export_symbol> es(new export_func(fn, func));
        es->symlst.push_back(es);

        return *es;
    }

    void export_symbol_to_scope(lua_State *L) const
    {
        luaglue::to_lua(L, m_fn); // push function name
        luaglue::to_lua(L, m_func); // push function
        lua_settable(L, -3); // pop key and value
    }

private:
    export_func(const std::string fn, T func)
        : m_fn(fn)
        , m_func(func)
    {
    }

    std::string     m_fn;
    T               m_func;
};

// export func
template <class TRET, class ...TARGS>
export_symbol& def(const std::string &fn, TRET (*func)(TARGS...))
{
    return export_func<function<TRET(TARGS...)> >::new_export_func(fn, func);
}

template <class TRET, class ...TARGS>
export_symbol& def(const std::string &fn, function<TRET(TARGS...)> func)
{
    return export_func<function<TRET(TARGS...)> >::new_export_func(fn, func);
}

// export class
template<class T>
inline void set_export_ref_func(lua_State *L, void (*func)(lua_State*, const T &))
{
    luaL_newmetatable(L, "export_ref_table");
    lua_pushlightuserdata(L, (void *)func);
    lua_setfield(L, -2, typeid(T).name());
    lua_pop(L, 1);
}

template<class T>
inline void (*get_export_ref_func(lua_State *L))(lua_State *, const T &)
{
    luaL_newmetatable (L, "export_ref_table"); // push table
    lua_getfield(L, -1, typeid(T).name()); // push light userdata
    void (*func)(lua_State *, const T &) = lua_topointer(L, -1);
    lua_pop(L, 2);

    return func;
}

template<class T>
inline void set_import_ref_func(lua_State *L, holder<T> (*func)(lua_State*, int))
{
    luaL_newmetatable(L, "import_ref_table");
    lua_pushlightuserdata(L, (void *)func);
    lua_setfield(L, -2, typeid(T).name());
    lua_pop(L, 1);
}

template<class T>
inline holder<T> (*get_import_ref_func(lua_State *L))(lua_State *, int)
{
    luaL_newmetatable (L, "import_ref_table"); // push table
    lua_getfield(L, -1, typeid(T).name()); // push light userdata
    holder<T> (*func)(lua_State *, int) = lua_topointer(L, -1);
    lua_pop(L, 2);

    return func;
}

class export_mem
{
public:
    export_mem(const std::string &name) : m_name(name) {}
    virtual void push(lua_State*) const = 0;
    virtual ~export_mem(){}
    inline const std::string &name(void) const
    {
        return m_name;
    }

protected:
    std::string m_name;
};

template <class T, class TSP, class TRET, class ...TARGS>
class em: public export_mem
{
public:
    em(const std::string &name, TRET (T::*mem)(TARGS ...))
        : export_mem(name)
        , m_mem([=](shared_ptr<TSP> sp, TARGS ...args){return (dynamic_cast<T*>(&*sp)->*mem)(args ...);})
    {}

    em(const std::string &name, TRET (T::*mem)(TARGS ...) const)
        : export_mem(name)
        , m_mem([=](shared_ptr<TSP> sp, TARGS ...args){return (dynamic_cast<T*>(&*sp)->*mem)(args ...);})
    {}

    void push(lua_State *L) const
    {
        to_lua(L, m_mem);
    }

private:
    function<TRET(shared_ptr<TSP>, TARGS ...)> m_mem;
};

template <class T, class TB, class TSP>
class export_class: public export_symbol
{
public:

    static export_class &new_export_class(const std::string fn)
    {
        shared_ptr<export_symbol> es(new export_class(fn));
        es->symlst.push_back(es);

        return dynamic_cast<export_class&>(*es);
    }

    void export_symbol_to_scope(lua_State *L) const
    {
/*
 *  引用类型导出有问题,暂时拿掉
*/
//        set_export_ref_func(L, export_ref);
//        set_import_ref_func(L, import_ref);

        get_weak_table<TSP>(L); // create weak table and push it
        get_raw_meta_table<T>(L); // create meta table and push it
        if (strcmp(typeid(T).name(), typeid(TB).name()))
        {
            get_raw_meta_table<TB>(L); // create or get parent's meta table, and push it
            lua_setmetatable(L, -2); // pop parent's meta table
        }
        for (auto mit = m_member.begin(); mit != m_member.end(); ++mit)
        {
            (*mit)->push(L);
            lua_setfield(L, -2, (*mit)->name().c_str());
        }
        lua_pop(L, 2); // clear the stack

        lua_newtable(L);
        for (typename std::list<TCONSTRUCTORMAP>::const_iterator cit = m_constructor.begin(); cit != m_constructor.end(); ++cit)
        {
            lua_pushcfunction(L, cit->func);
            lua_setfield(L, -2, cit->name.c_str());
        }
        lua_setfield(L, -2, m_class_name.c_str());
    }
    /*
    template <int a = 0>
    export_class &constructor(const std::string &name)
    {
        m_constructor.push_back(TCONSTRUCTORMAP(name, newT<>));

        return *this;
    }
 */
    template <class ...TARGS>
    export_class &constructor(const std::string &name)
    {
        m_constructor.push_back(TCONSTRUCTORMAP(name, newT<TARGS...>));

        return *this;
    }

    template <class TRET, class ... TARGS>
    export_class &def(const std::string &name, TRET(T::*mem)(TARGS ...))
    {
        m_member.push_back(shared_ptr<export_mem>(new em<T, TSP, TRET, TARGS ...>(name, mem)));
        return *this;
    }

    template <class TRET, class ... TARGS>
    export_class &def(const std::string &name, TRET(T::*mem)(TARGS ...) const)
    {
        m_member.push_back(shared_ptr<export_mem>(new em<T, TSP, TRET, TARGS ...>(name, mem)));
        return *this;
    }

private:
    std::string             m_class_name;

    struct TCONSTRUCTORMAP
    {
        TCONSTRUCTORMAP(const std::string &n, int (*f)(lua_State *))
            : name(n)
            , func(f)
        {
        }

        std::string name;
        int (*func)(lua_State *);
    };
    std::list<TCONSTRUCTORMAP> m_constructor;
    std::list<shared_ptr<export_mem> > m_member;

    export_class(const std::string &name)
        : m_class_name(name)
    {
    }

    template<int a = 0>
    static int newT(lua_State *L)
    {
        to_lua(L, shared_ptr<TSP>(new T));
        return 1;
    }

    template<class T_1>
    static int newT(lua_State *L)
    {
        holder<T_1> arg1 = from_lua<T_1>()(L, 1);
        return arg1 ? \
                    (to_lua(L, shared_ptr<TSP>(new T(*arg1))), 1) : 0;
    }

    template<class T_1, class T_2>
    static int newT(lua_State *L)
    {
        holder<T_1> arg1 = from_lua<T_1>()(L, 1);
        holder<T_2> arg2 = from_lua<T_2>()(L, 2);
        return (arg1 && arg2) ? \
                    (to_lua(L, shared_ptr<TSP>(new T(*arg1, *arg2))), 1) : 0;
    }
    //******************Int_li*****************************
    template<class T_1, class T_2, class T_3>
    static int newT(lua_State *L)
    {
        holder<T_1> arg1 = from_lua<T_1>()(L, 1);
        holder<T_2> arg2 = from_lua<T_2>()(L, 2);
        holder<T_3> arg3 = from_lua<T_3>()(L, 3);
        return (arg1 && arg2 && arg3) ? \
                    (to_lua(L, shared_ptr<TSP>(new T(*arg1, *arg2, *arg3))), 1) : 0;
    }

    template<class T_1, class T_2, class T_3, class T_4>
    static int newT(lua_State *L)
    {
        holder<T_1> arg1 = from_lua<T_1>()(L, 1);
        holder<T_2> arg2 = from_lua<T_2>()(L, 2);
        holder<T_3> arg3 = from_lua<T_3>()(L, 3);
        holder<T_4> arg4 = from_lua<T_4>()(L, 4);
        return (arg1 && arg2 && arg3 && arg4) ? \
                    (to_lua(L, shared_ptr<TSP>(new T(*arg1, *arg2, *arg3, *arg4))), 1) : 0;
    }

    template<class T_1, class T_2, class T_3, class T_4, class T_5>
    static int newT(lua_State *L)
    {
        holder<T_1> arg1 = from_lua<T_1>()(L, 1);
        holder<T_2> arg2 = from_lua<T_2>()(L, 2);
        holder<T_3> arg3 = from_lua<T_3>()(L, 3);
        holder<T_4> arg4 = from_lua<T_4>()(L, 4);
        holder<T_5> arg5 = from_lua<T_5>()(L, 5);
        return (arg1 && arg2 && arg3 && arg4 &&arg5) ? \
                    (to_lua(L, shared_ptr<TSP>(new T(*arg1, *arg2, *arg3, *arg4, *arg5))), 1) : 0;
    }

    template<class T_1, class T_2, class T_3, class T_4, class T_5, class T_6>
    static int newT(lua_State *L)
    {
        holder<T_1> arg1 = from_lua<T_1>()(L, 1);
        holder<T_2> arg2 = from_lua<T_2>()(L, 2);
        holder<T_3> arg3 = from_lua<T_3>()(L, 3);
        holder<T_4> arg4 = from_lua<T_4>()(L, 4);
        holder<T_5> arg5 = from_lua<T_5>()(L, 5);
        holder<T_6> arg6 = from_lua<T_6>()(L, 6);
        return (arg1 && arg2 && arg3 && arg4 && arg5 && arg6) ? \
                    (to_lua(L, shared_ptr<TSP>(new T(*arg1, *arg2, *arg3, *arg4, *arg5, *arg6))), 1) : 0;
    }

    template<class T_1, class T_2, class T_3, class T_4, class T_5, class T_6, class T_7>
    static int newT(lua_State *L)
    {
        holder<T_1> arg1 = from_lua<T_1>()(L, 1);
        holder<T_2> arg2 = from_lua<T_2>()(L, 2);
        holder<T_3> arg3 = from_lua<T_3>()(L, 3);
        holder<T_4> arg4 = from_lua<T_4>()(L, 4);
        holder<T_5> arg5 = from_lua<T_5>()(L, 5);
        holder<T_6> arg6 = from_lua<T_6>()(L, 6);
        holder<T_7> arg7 = from_lua<T_7>()(L, 7);
        return (arg1 && arg2 && arg3 && arg4 && arg5 && arg6 && arg7) ? \
                    (to_lua(L, shared_ptr<TSP>(new T(*arg1, *arg2, *arg3, *arg4, *arg5, *arg6, *arg7))), 1) : 0;
    }

    template<class T_1, class T_2, class T_3, class T_4, class T_5, class T_6, class T_7, class T_8>
    static int newT(lua_State *L)
    {
        holder<T_1> arg1 = from_lua<T_1>()(L, 1);
        holder<T_2> arg2 = from_lua<T_2>()(L, 2);
        holder<T_3> arg3 = from_lua<T_3>()(L, 3);
        holder<T_4> arg4 = from_lua<T_4>()(L, 4);
        holder<T_5> arg5 = from_lua<T_5>()(L, 5);
        holder<T_6> arg6 = from_lua<T_6>()(L, 6);
        holder<T_7> arg7 = from_lua<T_7>()(L, 7);
        holder<T_8> arg8 = from_lua<T_8>()(L, 8);
        return (arg1 && arg2 && arg3 && arg4 && arg5 && arg6 && arg7 && arg8) ? \
                    (to_lua(L, shared_ptr<TSP>(new T(*arg1, *arg2, *arg3, *arg4, *arg5, *arg6, *arg7, *arg8))), 1) : 0;
    }

    template<class T_1, class T_2, class T_3, class T_4, class T_5, class T_6, class T_7, class T_8, class T_9>
    static int newT(lua_State *L)
    {
        holder<T_1> arg1 = from_lua<T_1>()(L, 1);
        holder<T_2> arg2 = from_lua<T_2>()(L, 2);
        holder<T_3> arg3 = from_lua<T_3>()(L, 3);
        holder<T_4> arg4 = from_lua<T_4>()(L, 4);
        holder<T_5> arg5 = from_lua<T_5>()(L, 5);
        holder<T_6> arg6 = from_lua<T_6>()(L, 6);
        holder<T_7> arg7 = from_lua<T_7>()(L, 7);
        holder<T_8> arg8 = from_lua<T_8>()(L, 8);
        holder<T_9> arg9 = from_lua<T_9>()(L, 9);
        return (arg1 && arg2 && arg3 && arg4 && arg5 && arg6 && arg7 && arg8 && arg9) ? \
                    (to_lua(L, shared_ptr<TSP>(new T(*arg1, *arg2, *arg3, *arg4, *arg5, *arg6, *arg7, *arg8, *arg9))), 1) : 0;
    }

    template<class T_1, class T_2, class T_3, class T_4, class T_5, class T_6, class T_7, class T_8, class T_9, class T_10>
    static int newT(lua_State *L)
    {
        holder<T_1> arg1 = from_lua<T_1>()(L, 1);
        holder<T_2> arg2 = from_lua<T_2>()(L, 2);
        holder<T_3> arg3 = from_lua<T_3>()(L, 3);
        holder<T_4> arg4 = from_lua<T_4>()(L, 4);
        holder<T_5> arg5 = from_lua<T_5>()(L, 5);
        holder<T_6> arg6 = from_lua<T_6>()(L, 6);
        holder<T_7> arg7 = from_lua<T_7>()(L, 7);
        holder<T_8> arg8 = from_lua<T_8>()(L, 8);
        holder<T_9> arg9 = from_lua<T_9>()(L, 9);
        holder<T_10> arg10 = from_lua<T_10>()(L, 10);
        return (arg1 && arg2 && arg3 && arg4 && arg5 && arg6 && arg7 && arg8 && arg9 && arg10) ? \
                    (to_lua(L, shared_ptr<TSP>(new T(*arg1, *arg2, *arg3, *arg4, *arg5, *arg6, *arg7, *arg8, *arg9, *arg10))), 1) : 0;
    }
    //*******************************************************

    static void export_ref(lua_State *L, const T &ref)
    {
        shared_ptr<TSP> sp(new T(ref));

        to_lua(L, sp);
    }

    static holder<T> import_ref(lua_State *L, int index)
    {
        holder<shared_ptr<TSP> > hsp(from_lua<shared_ptr<TSP> >()(L, index));
        if(hsp)
        {
            return holder<T>(*dynamic_cast<T *>(&**hsp));
        }

        return holder<T>();
    }
};

template <class T, class TB, class TSP>
export_class<T, TB, TSP> &class_(const std::string &cn)
{
    return export_class<T, TB, TSP>::new_export_class(cn);
}

class _scope
{
public:
    _scope(const object &t)
        : m_scope(t)
    {
    }

    inline _scope scope(const std::string &sn)
    {
        object s(m_scope[sn]);

        if (LUA_TTABLE != s.type())
        {
            s = m_scope[sn] = object::new_table(m_scope.vm());
        }

        return s;
    }

    void operator[](const export_symbol &es)
    {
        lua_State *L = m_scope.vm();

        std::list<shared_ptr<export_symbol> >::const_iterator it = es.symlst.begin();
        std::list<shared_ptr<export_symbol> >::const_iterator end = es.symlst.end();

        m_scope.push(); // push scope
        for(; it!=end; ++it)
        {
            (*it)->export_symbol_to_scope(L);
        }
        lua_pop(L, 1); // pop scope
    }

private:
    object m_scope;
};

inline _scope scope(lua_State *L)
{
    return _scope(object::global(L));
}

inline _scope scope(const object &t)
{
    return _scope(t);
}

inline _scope scope(lua_State *L, const std::string & sn)
{
    return _scope(object::global(L)).scope(sn);
}

}

#endif // SCOPE_HPP
